#include "minimax.h"
#include "utility.h"
#include <algorithm>
#include <math.h>

using namespace std;


Minimax::Minimax(GameEngine *game_engine)
{
    m_game_engine = game_engine;

    m_white_node = new Node();
    m_white_node->set_board(m_game_engine->get_board(WHITE_PLAYER));
    m_white_node->set_father_node(NULL);
    m_white_node->set_parent_action(NULL);
    m_white_node->set_color(WHITE_PLAYER);

    m_black_node = new Node();
    m_black_node->set_board(m_game_engine->get_board(BLACK_PLAYER));
    m_black_node->set_color(BLACK_PLAYER);
    m_black_node->set_father_node(NULL);
    m_black_node->set_parent_action(NULL);
    default_depth = 5;
    r_count = 0;   

    white = black = 0;

    curr_black_node = m_black_node;
    curr_white_node = m_white_node;
}

void Minimax::start(){
    cpu_play();
}

void Minimax::cpu_play()
{
    if ( !m_game_engine->is_finished() ){
        if(  m_game_engine->get_current_player() == 0){
            play(curr_white_node, WHITE_PLAYER);
        } else{
            //play(curr_black_node, BLACK_PLAYER);
        }
    }

}

void Minimax::erase_action_list(){
    m_possible_actions.clear();
}

void Minimax::add_action(Action *action){
    m_possible_actions.push_back(action);
}

Action* Minimax::get_random_action(){
    int maior = -10;
    Action *aux = NULL;
    Action *best = NULL;
    for ( unsigned int i=0; i < m_possible_actions.size(); i++){
        aux =m_possible_actions.at(i);
        if ( aux->value > maior ){
            best = aux;
        }
    }
    return best;

    /*int i = 0;
    if ( m_possible_actions.size() != 0){
        i = rand()%m_possible_actions.size();
        return m_possible_actions.at(i);
    }
    return NULL;
    Action *next = NULL;
        for ( int i =0; i < m_possible_actions.size(); i++){
            next = m_possible_actions.at(i);
            if ( next->m_piece_x == 0 && next->m_piece_y == 0){
                return next;
            }
            if ( next->m_piece_x == 0 && next->m_piece_y == 7){
                return next;
            }
            if ( next->m_piece_x == 7 && next->m_piece_y == 0){
                return next;
            }
            if ( next->m_piece_x == 7 && next->m_piece_y == 7){
                return next;
            }

        }
        return next;
    */
}

void Minimax::set_next_action(Action *action){
    this->m_next_action = action;
}

Action* Minimax::get_next_action(){
    return m_next_action;
}

Node* Minimax::get_next_node(Node *node, Action *action){
    Node *next = new Node();
    //next->set_board(node->get_board());
    next->set_father_node(node);
    if ( m_game_engine->get_current_player() == 0){
        next->set_color(WHITE_PLAYER);
    } else {
        next->set_color(BLACK_PLAYER);
    }
    next->set_parent_action(action);
    return next;
}


player Minimax::get_opposite_player(player p){
    if ( p == WHITE_PLAYER){
        return BLACK_PLAYER;
    } else {
        return WHITE_PLAYER;
    }
}

Node* Minimax::play(Node *node, player p){
    backup_boards();
    call_alpha_beta(node, MAX_PLAYER, p, -1000000, 100000, 0);
    //process_best_action(node, p, MAX_PLAYER, 0);
    restore_boards();
    Action *a = get_random_action();
    if ( a != NULL){
        do_play(a);
        erase_action_list();

    } else {
        m_game_engine->game_finished();
    }
    return NULL;
}

void Minimax::restore_boards(){
    m_game_engine->m_white_board = backup_white_board;
    m_game_engine->m_white_score = backup_white_board.get_score();

    m_game_engine->m_black_board = backup_black_board;
    m_game_engine->m_black_score = backup_black_board.get_score();

    m_game_engine->m_curr_column = backup_curr_col  ;
    m_game_engine->m_curr_row = backup_curr_row   ;
    m_game_engine->m_num_moves = backup_num_moves  ;
    m_game_engine->m_valid_move = backup_valid_move ;

    m_game_engine->set_curr_player();
}

void Minimax::backup_boards(){
    backup_white_board = save_white_board();
    backup_black_board = save_black_board();

    backup_curr_col   =  m_game_engine->m_curr_column;
    backup_curr_row   =  m_game_engine->m_curr_row;
    backup_num_moves  =  m_game_engine->m_num_moves;
    backup_valid_move =  m_game_engine->m_valid_move;

}

int Minimax::process_best_action(Node *node, player p, role r, int depth){
    // if it's a final state
    if ( node->is_final()){
        return Utility::evaluate(node);
    }
    if ( depth >= default_depth){
        return Utility::evaluate(node);
    }
    // if the player role is MAX
    if ( r == MAX_PLAYER) {
        m_game_engine->set_player(p);
        return max_solver(node, p, depth);
    }
    // if the player role is MIN
    if ( r == MIN_PLAYER) {
        m_game_engine->set_player(p);
        return min_solver(node, p, depth);
    }

}

int Minimax::call_alpha_beta(Node *node, role r, player p, int alpha, int beta, int depth){
    if (node->is_final()){
        return Utility::evaluate(node);
    }
    if ( depth >= default_depth){
        return Utility::evaluate(node);
    }
    m_game_engine->set_player(p);
    return alpha_beta(node, r, p, alpha, beta, depth);
}

std::list<Action*> Minimax::get_action_list(std::vector<int> &moves){
    std::list<Action*> action_list;
    for ( unsigned int i =0; i < moves.size(); i+=2){
        Action *a = new Action(moves[i], moves[i+1], 0);
        action_list.push_back(a);
    }
    return action_list;
}

void Minimax::teste_play(Action *a){
    m_game_engine->teste_move(a->m_piece_x, a->m_piece_y);
}

void Minimax::do_play(Action *a){
    m_game_engine->verify_move(a->m_piece_x, a->m_piece_y);
}

int Minimax::max_solver(Node *node, player p, int depth){
    int max_result, successor_result;
       Node *next_node = NULL;
       std::list<Action*> action_list;
       Action *successor_action = NULL;
       list<Action*>::iterator list_iterator;
       std::vector<int> moves;
       max_result= -10000000;
       ReversiBoard temp_curr;
       ReversiBoard temp_opp ;
       m_game_engine->get_available_moves(moves);
       action_list = get_action_list(moves);

       if ( action_list.size() == 0){
           return Utility::evaluate(node);
       }

       //cout << "depth: " << depth << endl;
       if ( m_game_engine->get_current_player() == 0){
           temp_curr = save_white_board();
           temp_opp = save_black_board();



       } else{
           temp_curr = save_black_board();
           temp_opp = save_white_board();

       }


       // for each node derivated from an action, evaluate his value and return the maximum value
       for ( list_iterator = action_list.begin(); list_iterator != action_list.end(); list_iterator++){
           if ( m_game_engine->get_current_player() == 0){
               m_game_engine->m_white_board = temp_curr;
               m_game_engine->m_black_board = temp_opp;
               m_game_engine->set_curr_player();

               /*cout << "current: WHITE" << endl;
               cout << "board: " << endl;
               m_game_engine->m_curr_board->print();

               cout << "opp: BLACK" << endl;
               cout << "board: " << endl;
               m_game_engine->m_opp_board->print();*/
            } else {
               m_game_engine->m_black_board = temp_curr;
               m_game_engine->m_white_board = temp_opp;
               m_game_engine->set_curr_player();

               /*cout << "current: BLACK" << endl;
               cout << "board: " << endl;
               m_game_engine->m_curr_board->print();

               cout << "opp: WHITE" << endl;
               cout << "board: " << endl;
               m_game_engine->m_opp_board->print();*/
            }

           successor_action = *list_iterator;
           next_node = get_next_node(node, successor_action);
           teste_play(successor_action);

           /*cout << "action: " << successor_action->m_piece_x << ", " << successor_action->m_piece_y << endl;
           if ( p == WHITE_PLAYER){
                cout << "WHITE played!" << endl;
           } else {
                cout << "BLACK played!" << endl;
            }
           m_game_engine->m_curr_board->print();*/

           next_node->set_board(m_game_engine->m_curr_board);
           successor_result = process_best_action(next_node, get_opposite_player(p), MIN_PLAYER, depth+1);

           if ( max_result < successor_result ) {
               //set_next_action(successor_action);
               successor_action->value = successor_result;
               if ( depth == 0){
                   add_action(successor_action);
               }
           }
           max_result = max(max_result, successor_result);
           m_game_engine->set_player(p);

       }

       free(next_node);
       return (max_result);
}


ReversiBoard Minimax::save_white_board() {
    ReversiBoard temp = ReversiBoard(m_game_engine->m_white_board.get_player_color());
    for ( int i = 0; i<8; i++){
        for  (int j =0; j<8; j++){
            temp.set_piece(i,j, m_game_engine->m_white_board.get_piece(i,j));
        }
    }
    temp.set_score(m_game_engine->m_white_board.get_score());
    cout << "";
    return temp;
}

ReversiBoard Minimax::save_black_board(){
    ReversiBoard temp = ReversiBoard(m_game_engine->m_black_board.get_player_color());
    for ( int i = 0; i<8; i++){
        for  (int j =0; j<8; j++){
            temp.set_piece(i,j, m_game_engine->m_black_board.get_piece(i,j));
        }
    }
    temp.set_score(m_game_engine->m_black_board.get_score());
    return temp;
}

int Minimax::min_solver(Node *node, player p, int depth){
    int min_result, successor_result;
    Node *next_node = NULL;
    std::list<Action*> action_list;
    Action *successor_action = NULL;
    list<Action*>::iterator list_iterator;
    ReversiBoard temp_curr;
    ReversiBoard temp_opp ;
    std::vector<int> moves;
    m_game_engine->get_available_moves(moves);
    action_list = get_action_list(moves);

    min_result = 1000000;

    if ( action_list.size() == 0){
        return Utility::evaluate(node);
    }

   // cout << "depth: " << depth << endl;
    if ( m_game_engine->get_current_player() == 0){
        temp_curr = save_white_board();
        temp_opp = save_black_board();
    } else{
        temp_curr = save_black_board();
        temp_opp = save_white_board();
    }

    // for each node derivated from an action, evaluate his value and return the minimum value
    for ( list_iterator = action_list.begin(); list_iterator != action_list.end(); list_iterator++){
        if ( m_game_engine->get_current_player() == 0){
            m_game_engine->m_white_board = temp_curr;
            m_game_engine->m_black_board = temp_opp;
            m_game_engine->set_curr_player();

            /*cout << "current: WHITE" << endl;
            cout << "board: " << endl;
            m_game_engine->m_curr_board->print();

            cout << "opp: BLACK" << endl;
            cout << "board: " << endl;
            m_game_engine->m_opp_board->print();*/
         } else {
            m_game_engine->m_black_board = temp_curr;
            m_game_engine->m_white_board = temp_opp;
            m_game_engine->set_curr_player();

            /*cout << "current: BLACK" << endl;
            cout << "board: " << endl;
            m_game_engine->m_curr_board->print();

            cout << "opp: WHITE" << endl;
            cout << "board: " << endl;
            m_game_engine->m_opp_board->print();*/
         }

        successor_action = *list_iterator;
        next_node = get_next_node(node, successor_action);
        teste_play(successor_action);

        //update_boards(next_node, p);

        /*cout << "action: " << successor_action->m_piece_x << ", " << successor_action->m_piece_y << endl;
        if ( p == WHITE_PLAYER){
             cout << "WHITE played!" << endl;
        } else {
             cout << "BLACK played!" << endl;
        }
        m_game_engine->m_curr_board->print();*/

        next_node->set_board(m_game_engine->m_curr_board);
        successor_result = process_best_action(next_node, get_opposite_player(p), MAX_PLAYER, depth+1);

        if ( min_result > successor_result ) {
            successor_action->value = successor_result;
            //set_next_action(successor_action);
            if ( depth == 0){
                add_action(successor_action);
            }
        }
        min_result = min(min_result, successor_result);
        m_game_engine->set_player(p);
    }

    return (min_result);
}

int Minimax::alpha_beta(Node *node, role r, player p, int alpha, int beta, int depth){
    int evaluation;
    Node *next_node = NULL;
    std::list<Action*> action_list;
    Action *successor_action = NULL;
    list<Action*>::iterator list_iterator;
    ReversiBoard temp_curr;
    ReversiBoard temp_opp ;
    std::vector<int> moves;
    m_game_engine->get_available_moves(moves);
    action_list = get_action_list(moves);

    if ( r == MAX_PLAYER){
        if ( action_list.size() == 0){
            return Utility::evaluate(node);
        }
        if ( m_game_engine->get_current_player() == 0){
            temp_curr = save_white_board();
            temp_opp = save_black_board();
        } else {
            temp_curr = save_black_board();
            temp_opp = save_white_board();
        }
        // for each node derivated from an action, evaluate his value and return the maximum value
        for ( list_iterator = action_list.begin(); list_iterator != action_list.end(); list_iterator++){
            //cout << "depth: " << depth << endl;
            if ( m_game_engine->get_current_player() == 0){
                m_game_engine->m_white_board = temp_curr;
                m_game_engine->m_black_board = temp_opp;
                m_game_engine->set_curr_player();

                cout << "current: WHITE" << endl;
                cout << "board: " << endl;
                m_game_engine->m_curr_board->print();

                cout << "opp: BLACK" << endl;
                cout << "board: " << endl;
                m_game_engine->m_opp_board->print();


            } else {
                m_game_engine->m_black_board = temp_curr;
                m_game_engine->m_white_board = temp_opp;
                m_game_engine->set_curr_player();

              cout << "current: BLACK" << endl;
                cout << "board: " << endl;
                m_game_engine->m_curr_board->print();

                cout << "opp: WHITE" << endl;
                cout << "board: " << endl;
                m_game_engine->m_opp_board->print();
            }

            successor_action = *list_iterator;
            next_node = get_next_node(node, successor_action);
            teste_play(successor_action);

            next_node->set_board(m_game_engine->m_curr_board);

            cout << "action: " << successor_action->m_piece_x << ", " << successor_action->m_piece_y << endl;
            if ( p == WHITE_PLAYER){
                 cout << "WHITE played!" << endl;
            } else {
                 cout << "BLACK played!" << endl;
             }
            m_game_engine->m_curr_board->print();

            //evaluation = alpha_beta(next_node, MIN_PLAYER, get_opposite_player(p), alpha, beta, depth+1);
            evaluation = call_alpha_beta(next_node, MIN_PLAYER, get_opposite_player(p), alpha, beta, depth+1);
            if ( evaluation > alpha){
                alpha = evaluation;
                if ( depth == 0){
                    add_action(successor_action);
                }
            }
            if ( alpha >= beta){
                return alpha;
            }
            m_game_engine->set_player(p);
        }
        return alpha;

    } else {
        if ( action_list.size() == 0){
            return Utility::evaluate(node);
        }

        if ( m_game_engine->get_current_player() == 0){
            temp_curr = save_white_board();
            temp_opp = save_black_board();
        } else{
            temp_curr = save_black_board();
            temp_opp = save_white_board();
        }

        // for each node derivated from an action, evaluate his value and return the minimum value
        for ( list_iterator = action_list.begin(); list_iterator != action_list.end(); list_iterator++){
            //cout << "depth: " << depth << endl;
            if ( m_game_engine->get_current_player() == 0){
                m_game_engine->m_white_board = temp_curr;
                m_game_engine->m_black_board = temp_opp;
                m_game_engine->set_curr_player();

                cout << "current: WHITE" << endl;
                cout << "board: " << endl;
                m_game_engine->m_curr_board->print();

                cout << "opp: BLACK" << endl;
                cout << "board: " << endl;
                m_game_engine->m_opp_board->print();


             } else {
                m_game_engine->m_black_board = temp_curr;
                m_game_engine->m_white_board = temp_opp;
                m_game_engine->set_curr_player();

                cout << "current: BLACK" << endl;
                cout << "board: " << endl;
                m_game_engine->m_curr_board->print();

                cout << "opp: WHITE" << endl;
                cout << "board: " << endl;
                m_game_engine->m_opp_board->print();

             }

            successor_action = *list_iterator;
            next_node = get_next_node(node, successor_action);
            teste_play(successor_action);

            next_node->set_board(m_game_engine->m_curr_board);


            cout << "action: " << successor_action->m_piece_x << ", " << successor_action->m_piece_y << endl;
            if ( p == WHITE_PLAYER){
                 cout << "WHITE played!" << endl;
            } else {
                 cout << "BLACK played!" << endl;
             }
            m_game_engine->m_curr_board->print();

            //evaluation = alpha_beta(next_node, MAX_PLAYER, get_opposite_player(p), alpha, beta, depth+1);
            evaluation = call_alpha_beta(next_node, MAX_PLAYER, get_opposite_player(p), alpha, beta, depth+1);
            if ( evaluation < beta){
                beta = evaluation;
                if ( depth == 0){
                    add_action(successor_action);
                }
            }
            if ( alpha >= beta){
                return alpha;
            }
            m_game_engine->set_player(p);
        }
        return beta;

    }
}

/*
function alphabeta(node, depth, α, β, Player)
    if  depth = 0 or node is a terminal node
        return the heuristic value of node
    if  Player = MaxPlayer
        for each child of node
            α := max(α, alphabeta(child, depth-1, α, β, not(Player) ))
            if β ≤ α
                break                             (* Beta cut-off *)
        return α
    else
        for each child of node
            β := min(β, alphabeta(child, depth-1, α, β, not(Player) ))
            if β ≤ α
                break                             (* Alpha cut-off *)
        return β
*/
